昨天我們分析了狀態變更的規則 & 狀態圖,今天我們來將昨天的分析實做出來。
// 讀取檔案內容 
const fs = require('fs');
const str = fs.readFileSync('.env-sample', 'utf8');
// 切分成字元陣列
const charList = str.split('');
let collected = '';
while (charList.length > 0) {
    const current = charList.shift();
    
    /*
    * 在這裡處理特殊字元對應要做什麼事
    */
    // 如果沒遇到特殊字元 , 就將 char 收集起來
    if(current !== '\r') collected += current;
}
const STATUS = {
    NORMAL: 0,
    IN_QUOTATION: 1,
    IN_COMMENT: 2,
    AFTER_EQUAL: 3,
}
const tokens = [];
const resetCollect = () => collected = '';
4.1 - NORMAL 的狀態變化
const handle_NORMAL = current => {
    // NORMAL 狀態時遇到 `#` ,切換狀態為 IN_COMMENT
    if (current === '#') {
        CURR_STATUS = STATUS.IN_COMMENT;
    }
    
    // NORMAL 狀態時遇到 `"` ,切換狀態為 IN_QUOTATION
    if (current === '"') {
        CURR_STATUS = STATUS.IN_QUOTATION;
    }
    
    // NORMAL 狀態時遇到 `=` ,切換狀態為 AFTER_EQUAL,並將 collected 中的文字當作 key,然後清空 collected 變數內的文字
    if (current === '=') {
        CURR_STATUS = STATUS.AFTER_EQUAL; // change to after equal status ( collect value )
        tokens.push({type: 'key', value: collected}); // the collected is key
        resetCollect();
    }
    
    // NORMAL 狀態時遇到 `\n` ,不做處理,收集下個字元
    if (current === '\n') {
        // do nothing
    }
}
4.2 - IN_QUOTATION 的狀態變化
const handle_IN_QUOTATION = current => {
    // IN_QUOTATION 狀態時遇到 `=` . `\n` . `#` ,不做處理,收集下個字元
    if (current === '\n' || current === '=' || current === '#') {
        // do nothing
    }
    // IN_QUOTATION 狀態時遇到 `"` ,切換狀態為 NORMAL,並將收集的文字當作 value,然後清空 collected 變數內的文字
    if (current === '"') {
        CURR_STATUS = 0;
        tokens.push({type: 'value', value: collected});
        resetCollect();
    }
}
4.3 - IN_COMMENT 的狀態變化
const handle_IN_COMMENT = current => {
    // IN_COMMENT 狀態時遇到 `=` . `"` . `#` ,不做處理,收集下個字元
    if (current === '"' || current === '=' || current === '#') {
        // do nothing
    }
    // IN_COMMENT 狀態時遇到 `\n` ,切換狀態為 NORMAL,並將收集的文字當作 value,然後清空 collected 變數內的文字
    if (current === '\n') {
        CURR_STATUS = STATUS.NORMAL;
        tokens.push({type: 'comment', value:'#'+ collected});
        resetCollect();
    }
}
4.4 - AFTER_EQUAL 的狀態變化
const handle_AFTER_EQUAL = current => {
    // AFTER_EQUAL 狀態時遇到 `=` . `"` . `#` ,不做處理,收集下個字元
    if ( current === '=' || current === '#') {
        // do nothing
    }
    // AFTER_EQUAL 狀態時遇到 `"` ,切換狀態為 IN_QUOTATION
    if (current === '"') {
        CURR_STATUS = STATUS.IN_QUOTATION;
    }
    // AFTER_EQUAL 狀態時遇到 `#` ,切換狀態為 NORMAL,並將 collected 中的文字當作 key,然後清空 collected 變數內的文字
    if (current === '#') {
        CURR_STATUS = STATUS.IN_COMMENT;
        tokens.push({type: 'value', value: collected});
        resetCollect();
    }
    // AFTER_EQUAL 狀態時遇到 `\n` ,切換狀態為 NORMAL,並將收集的文字當作 value,然後清空 collected 變數內的文字
    if (current === '\n') {
        CURR_STATUS = STATUS.NORMAL;
        tokens.push({type: 'value', value: collected});
    }
}
將上面的區塊做整合,就可以得到 完整程式碼 myDotEnvTokenizer.js
// 讀取檔案內容 
const fs = require('fs');
const str = fs.readFileSync('.env-sample', 'utf8');
// 切分成字元陣列
const charList = str.split('');
// 狀態列表
const STATUS = {
    NORMAL: 0,
    IN_QUOTATION: 1,
    IN_COMMENT: 2,
    AFTER_EQUAL: 3,
}
// 收集資訊的變數
let collected = '';
let CURR_STATUS = STATUS.NORMAL;
// tokens 陣列
const tokens = [];
// 清空 collected 函式
const resetCollect = () => collected = '';
while (charList.length > 0) {
    const current = charList.shift();
    if(CURR_STATUS === STATUS.NORMAL) handle_NORMAL(current);
    if(CURR_STATUS === STATUS.IN_QUOTATION) handle_IN_QUOTATION(current);
    if(CURR_STATUS === STATUS.IN_COMMENT) handle_IN_COMMENT(current);
    if(CURR_STATUS === STATUS.AFTER_EQUAL) handle_AFTER_EQUAL(current);
    // 如果沒遇到特殊字元 , 就將 char 收集起來
    if(current !== '\r') collected += current;
}
另外想看追加例外處理的部分可到 GITHUB 查看
今天我們花了幾天的時間,終於實做出 env 的 Tokenizer,相信邦友們一定沒有忘記,我們期望設定給 process.env 的是 AST ,而不是今天的 Tokens。
AST
const AST = {
    SECRET_KEY: 'YOURSECRETKEYGOESHERE',
    SECRET_HASH: 'something-with-a-#-hash' ,
    PRIVATE_KEY: '-----BEGIN RSA PRIVATE KEY-----\n...\nKh9NV...\n...\n#### 5678\n-----END DSA PRIVATE KEY-----' ,
}
Tokens
const tokens = [
    { "type": "comment", "value": "# .env-sample file" },
    { "type": "comment", "value": "# This is a comment" },
    { "type": "key", "value": "SECRET_KEY" },
    { "type": "value", "value": "YOURSECRETKEYGOESHERE" },
    { "type": "comment", "value": "# comment" },
    { "type": "key", "value": "SECRET_HASH" },
    { "type": "value", "value": "something-with-a-#-hash" },
    { "type": "key", "value": "PRIVATE_KEY" },
    { "type": "value", "value": "-----BEGIN RSA PRIVATE KEY-----\n...\nKh9NV...\n...\n#### 5678\n-----END DSA PRIVATE KEY-----" }
]
明天我們就來說明 Tokens 要如何轉換成 AST 吧 ~